﻿using System;
using System.Collections.Generic;
using System.Text;

using Effects.Effects.GuiParts;
using Effects.Effects.Tools;

namespace Effects.Effects
{
    /// <summary>
    ///  Flanger effect.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   Flanger is very interesting effect. A real favourite for Eddie van Halen in 80's. 
    ///  </para>
    ///  <para>
    ///   It is believed that it has been invented by The Beatles during some recording session in the spring of 1966. 
    ///   They played two tapes with the same signal and slowed one of them by touching it. Then they slowed the other one, so they were synchronous again.
    ///   A little bit easier way is to control speed of the tape with LFO. The LFO actually controls delay time of the second signal.
    ///  </para>
    ///  <para>
    ///   In the implementation we use Oscillatting delay line, that has delay time controled by LFO. It buffers 10 ms of singal and provides interpolation in between sampling points.
    ///  </para>
    /// </remarks>
    public class Flanger:Effect
    {
        #region consts
        /// <summary>
        ///  Length of delay line in milisecond.
        /// </summary>
        protected const double flangerDelayTime = 10;

        #endregion

        #region controls
        // parameters controlled by user

        #region footswitch

        /// <summary>
        ///  Footswitch for by passing effect.
        /// </summary>
        private Footswitch footswitch;

        /// <summary>
        ///  Led indicating On/Off state.
        /// </summary>
        private LED led;

        /// <summary>
        ///  Synchornizer for footswitch and led.
        /// </summary>
        private FootswitchLedSynchronizer synchronizer;


        /// <summary>
        ///  Key for footswitch settings.
        /// </summary>
        private int footswitchKey = 0;

        #endregion

        #region speed

        /// <summary>
        ///  Pot adjusting speed parameter.
        /// </summary>
        protected Pot Speed;

        /// <summary>
        ///  Speed - period of oscillation that controls delay time.
        /// </summary>
        private int speed;

        /// <summary>
        ///  Key for speed parameter.
        /// </summary>
        private const int speedKey = 1;

        #endregion

        #region depth

        /// <summary>
        ///  Pot adjusting depth parameter - dry/wet mix
        /// </summary>
        protected Pot Depth;

        /// <summary>
        ///  depth - dry.wet mix
        /// </summary>
        private double depth;

        /// <summary>
        ///  equal to 1 - depth.
        /// </summary>
        /// <remarks> 
        ///  Having this value precomputed saves us lots of operations.
        /// </remarks>
        private double depth_1;

        /// <summary>
        ///  Key for depth setting
        /// </summary>
        private const int depthKey = 2;

        #endregion

        #region feedback
        /// <summary>
        ///  Pot adjusting feedback parameter.
        /// </summary>
        private Pot Feedback;

        /// <summary>
        ///  Feedback parameter. Controls mixing output of delay line to it's input.
        /// </summary>
        private double feedback;

        /// <summary>
        ///  Key for feedback setting.
        /// </summary>
        private const int feedbackKey = 3;

        #endregion

        #region oscillation_type

        /// <summary>
        ///  switch that is used to change oscilation shape.
        /// </summary>
        protected Switch oscilatorSwitch;

        /// <summary>
        ///  Key for oscillation type setting.
        /// </summary>
        private const int oscilatorSwitchKey = 4;

        /// <summary>
        ///  Indicates current oscillation type. 0 - triangle, 1 - sinus
        /// </summary>
        private int oscillationType;

        #endregion

        #endregion

        #region algorithm_fields
        // necessary fields for algorithm

        /// <summary>
        ///  delay line with time varying length according to LFO.
        /// </summary>
        private OscillatingDelayLine buffer;

        /// <summary>
        ///  time interval between 2 adjecant samples from input signal.
        /// </summary>
        private double step;

        #endregion

        #region effects_properties
        // overridden preperties of base class Effect

        /// <summary>
        ///  Gets or sets the sample rate.
        /// </summary>
        /// <remarks>
        ///  Change of sample rate causes also change of step and length of buffer.
        /// </remarks>
        public override uint SampleRate
        {
            get { return sampleRate; }
            set 
            {
                // must create new delay line
                sampleRate = value;
                step = 1000 / (double)sampleRate;
                buffer = new OscillatingDelayLine(flangerDelayTime, (int) sampleRate, buffer.Oscillator, 0);
                buffer.Oscillator.Period = speed;
            }
        }

        /// <summary>
        ///  Gets the name of the effect.
        /// </summary>
        public override string Name
        {
            get
            {
                return "Flanger v1.5";
            }
        }

        #endregion

        #region contructor
        // constructor and initialization of controls

        /// <summary>
        ///  Creates new instance of Flanger.
        /// </summary>
        /// <remarks>
        ///  Creates new instance with initial settings. It is necessary to set sample rate to work properly.
        /// </remarks>
        public Flanger()
        {
            // create buffer
            buffer = new OscillatingDelayLine(flangerDelayTime, 44100, new TriangleOscilator(), 0);

            // create pots and prepare appearance
            InitializeComponents();
            InitializeFootswitch();
            this.BackgroundImage =System.Drawing.Image.FromStream(new System.IO.MemoryStream(global::Effects.Recources.EffectBackgorunds.Flanger));
            this.Size = BackgroundImage.Size;
        }

        /// <summary>
        ///  Initializes all pots and oscillator switch.
        /// </summary>
        private void InitializeComponents()
        {
            Speed = new Pot();
            Speed.MaxValue=5000;
            Speed.MinValue=1;
            Speed.ValueChanged += new PotValueChangedEventHandler(this.SpeedPotChanged);
            Speed.Location = new System.Drawing.Point(5, 38);
            Speed.Knob = PotKnob.ChickenKnobMiddle;
            //Speed.Size = new System.Drawing.Size(110, 110);

            Depth = new Pot();
            Depth.MaxValue = 1;
            Depth.MinValue = 0;
            Depth.ValueChanged += new PotValueChangedEventHandler(this.DepthPotChanged);
            Depth.Location = new System.Drawing.Point(220, 38);
            Depth.Knob = PotKnob.ChickenKnobMiddle;
            //Depth.Size = new System.Drawing.Size(110, 110);

            Feedback = new Pot();
            Feedback.MinValue = -1;
            Feedback.MaxValue = 1;
            Feedback.ValueChanged += new PotValueChangedEventHandler(this.FeedbackPotChanged);
            Feedback.Location = new System.Drawing.Point(110, 38);
            Feedback.Knob = PotKnob.ChickenKnobMiddle;

            oscilatorSwitch = new Switch(2);
            oscilatorSwitch.Location = new System.Drawing.Point(83, 120);
            oscilatorSwitch.Height = 20;
            oscilatorSwitch.Width = 20;
            oscilatorSwitch.OnSwitch += new SwitchSwitched(this.OscilatorSwitchSwitched);
            oscilatorSwitch.SwitchKnobColor = System.Drawing.Color.FromArgb(93, 215, 27);

            this.Controls.Add(Speed);
            this.Controls.Add(Depth);
            this.Controls.Add(Feedback);
            this.Controls.Add(oscilatorSwitch);
            Speed.Reset();
            Depth.Reset();
            Feedback.Reset();
        }

        /// <summary>
        ///  Iniializes footswitch and led.
        /// </summary>
        private void InitializeFootswitch()
        {
            // footswitch
            footswitch = new Footswitch();
            switchedOn = true;
            footswitch.On = true;
            footswitch.Switched += new FootswitchSwitchedEventHandler(this.FootswitchSwitched);
            footswitch.Location = new System.Drawing.Point(185, 110);
            Controls.Add(footswitch);

            // led
            led = new LED();
            led.On = true;
            led.Location = new System.Drawing.Point(190, 40);
            Controls.Add(led);

            // synchronization
            synchronizer = new FootswitchLedSynchronizer(footswitch, led);
        }

        #endregion

        #region main_work

        // Overriding of effect method
        /// <summary>
        ///  Key method of Flanger. Modifies input signal
        /// </summary>
        /// <param name="data"> Input signal </param>
        /// <returns> Modified input signal </returns>
        public override double[] effect(double[] data)
        {
            if (switchedOn)
            {
                // process given data
                double[] outData = new double[data.Length];
                for (int i = 0; i < data.Length; i++)
                {
                    //data[i] *= 0.5;
                    // store sample, get delayed and mix it with the one actually stored
                    outData[i] = depth_1 * data[i] + depth * buffer.Step(data[i]);

                }

                // process remaining when counting with overlap
                return outData;
            }
            else
            {
                // just pass input signal to the delay line and return dry input
                for (int i = 0; i < data.Length; i++)
                    buffer.Step(data[i]);

                return data;
            }
        }

        #endregion

        #region cotrols_event_handler
        // handling events from controls

        /// <summary>
        ///  Speed pot event handler. Adjusts period of oscillation.
        /// </summary>
        /// <param name="sender"> Object that has raised the event == Speed pot </param>
        /// <param name="e"> Event arguments </param>
        private void SpeedPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.buffer.Oscillator.Period = e.NewValueInt;
            this.speed = e.NewValueInt;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(speedKey, speed);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Depth pot event handler. Adjust dry/wet mix
        /// </summary>
        /// <param name="sender"> Object that has raised the event == Depth pot </param>
        /// <param name="e"> Event arguments </param>
        private void DepthPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.depth = e.NewValue;
            depth_1 = 1 - depth;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(depthKey, depth);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Feedback pot event handler. Adjusts feddback of <see cref="Flanger.buffer"/> delay line.
        /// </summary>
        /// <param name="sender"> Object that has raised the event == Feedback pot </param>
        /// <param name="e"> Event arguments </param>
        private void FeedbackPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.feedback = e.NewValue;
            this.buffer.Feedback = feedback;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(feedbackKey, feedback);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Sets oscillator to buffer according to the given state.
        /// </summary>
        /// <param name="state"> Oscillation type: 0 - triangle, 1 - sin </param>
        protected void SetOscillator(int state)
        {
            Oscillator newOscilator = null;
            // create new oscillator according to the state
            switch (state)
            {

                case 0:
                    // triangle oscilator
                    newOscilator = new TriangleOscilator();
                    break;
                case 1:
                    //sin oscilator
                    newOscilator = new SinOscilator();
                    break;
                case 2:
                    break;
            }

            // period stays the same
            newOscilator.Period = buffer.Oscillator.Period;

            // set oscillator to the buffer
            buffer.Oscillator = newOscilator;
            oscillationType = state;
        }

        /// <summary>
        ///  Oscilator switch event handler. Changes oscillation type.
        /// </summary>
        /// <param name="sender"> Object that has raised the event == oscillatorSwitch switch </param>
        /// <param name="e"> Event arguments </param>
        public void OscilatorSwitchSwitched(object sender, SwitchEventArgs e)
        {
            // set oscillator
            SetOscillator(e.State);

            // raise event
            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(oscilatorSwitchKey, e.State);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Footswitch.Switched event handler. According to the event arguments signal bypasses or runs through the effect.
        /// </summary>
        /// <param name="sender"> Object that has raised the event. </param>
        /// <param name="args"> Event Arguments </param>
        private void FootswitchSwitched(object sender, FootswitchSwitchedEventArgs args)
        {
            this.switchedOn = args.On;

            // raise the event
            if (this.SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(footswitchKey, switchedOn);
                this.SettingsChanged(this, changeArgs);
            }
        }

        #endregion

        #region settings
        // getting and setting the settings of Tremolo

        /// <summary>
        ///  Raised when any parameter of effect is changed.
        /// </summary>
        public override event EffectSettingsChangedDelegate SettingsChanged;


        /// <summary>
        ///  Gets all current settings of the effect.
        /// </summary>
        /// <returns> All current seetings </returns>
        public override object[] GetAllSettings()
        {
            object[] settings = new object[5];
            settings[0] = switchedOn;
            settings[1] = depth;
            settings[2] = speed;
            settings[3] = feedback;
            settings[4] = oscillationType;
            return settings;
        }

        /// <summary>
        ///  Sets all settings.
        /// </summary>
        /// <param name="settings"> All settings of the effect. This argument should be output of GetAllSettings method that has been called previously. </param>
        public override void SetAllSettings(object[] settings)
        {
            object[] settingsArray = (object[])settings;
            
            this.switchedOn = (bool) settingsArray[0];
            synchronizer.SetValue(switchedOn);

            this.depth = (double)settingsArray[1];
            depth_1 = 1 - depth;
            Depth.SetValue(depth);

            this.speed = (int)settingsArray[2];
            buffer.Oscillator.Period = speed;
            Speed.SetValue(speed);

            this.feedback = (double)settingsArray[3];
            buffer.Feedback = feedback;
            Feedback.SetValue(feedback);

            SetOscillator((int)settingsArray[4]);
            oscilatorSwitch.State = oscillationType;
        }

        /// <summary>
        ///  Sets one settings
        /// </summary>
        /// <param name="e"> Setting change to be done </param>
        public override void SetSetting(EffectSettingsChangedEventArgs e)
        {
            int key = (int)e.Key;

            if (key == depthKey)
            {
                this.depth = (double)e.Value;
                depth_1 = 1 - depth;
                Depth.SetValue(depth);
                return;
            }

            if (key == speedKey)
            {
                this.speed = (int)e.Value;
                buffer.Oscillator.Period = speed;
                Speed.SetValue(speed);

                return;
            }

            if (key == feedbackKey)
            {
                this.feedback = (double)e.Value;
                buffer.Feedback = feedback;
                Feedback.SetValue(feedback);

                return;
            }

            if (key == oscilatorSwitchKey)
            {
                SetOscillator((int)e.Value);
                oscilatorSwitch.State = oscillationType;
                return;
            }

            if (key == footswitchKey)
            {
                switchedOn = (bool)e.Value;
                synchronizer.SetValue(switchedOn);
                return;
            }
        }

        /// <summary>
        ///  Gets parameter description.
        /// </summary>
        /// <returns> EffectParam array descrobing effect's parameters. </returns>
        public override EffectParam[] GetParameters()
        {
            EffectParam[] parameters = new EffectParam[5];

            ListItem[] footswitchState = new ListItem[2];
            footswitchState[0] = new ListItem("Turn on", true);
            footswitchState[1] = new ListItem("Turn off", false);
            parameters[0] = new EffectParam(footswitchKey, "ON/OFF", footswitchState);

            parameters[1] = new EffectParam(depthKey, "Depth", 0F, 1F);
            parameters[2] = new EffectParam(speedKey, "Speed", 1, 5000);
            parameters[3] = new EffectParam(feedbackKey, "FeedBack", -1F, 1F);
            
            ListItem[] oscillationTypes = new ListItem[2];
            oscillationTypes[1] = new ListItem("Triangle wave", 0);
            oscillationTypes[0] = new ListItem("Sine Wave",1);
            parameters[4] = new EffectParam(oscilatorSwitchKey, "Oscillation Type", oscillationTypes);

            return parameters;
        }

        #endregion
    }
}
